/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package protect03;

import java.io.*;
import java.math.BigInteger;
import java.util.Random;

/**
 *
 * @author reshetnikovk
 */
public class ElGamal {
    
    public static KeyHolder generateKeys(BigInteger pBase, BigInteger gxBase) {
        Random rnd = new Random();
        
        //РЎРЅР°С‡Р°Р»Р° Р±РµСЂРµС‚СЃСЏ РїСЂРѕСЃС‚Рѕ С‡РёСЃР»Рѕ p
        //BigInteger p = BigInteger.valueOf(13);
        BigInteger p = new BigInteger(pBase.bitLength(), rnd).add(pBase).nextProbablePrime();
        println("p = %s", p);
        
        //Р� СЃР»СѓС‡Р°Р№РЅРѕРµ РїСЂРѕСЃС‚Рѕ С‡РёСЃР»Рѕ g, g < p
        //BigInteger g = BigInteger.valueOf(2);
        BigInteger g = new BigInteger(gxBase.bitLength(), rnd).add(gxBase).nextProbablePrime();
        println("g = %s", g);
        
        //Р� СЃР»СѓС‡Р°Р№РЅРѕРµ РїСЂРѕСЃС‚Рѕ С‡РёСЃР»Рѕ x, x < p
        //BigInteger x = BigInteger.valueOf(7);
        BigInteger x = new BigInteger(gxBase.bitLength(), rnd).add(gxBase).nextProbablePrime();
        println("x = %s", x);
        
        //Р—Р°С‚РµРј РІС‹С‡РёСЃР»РёРј y
        BigInteger y = g.modPow(x, p); //Math.round(Math.pow(g, x)) % p;
        println(String.format("y = %s", y));
        
        //y, g, p - СЌС‚Рѕ РѕР±С‰РµРґРѕСЃС‚СѓРїРЅС‹Рµ РєР»СЋС‡Рё
        //x - СЃРµРєСЂРµС‚РЅС‹Р№ РєР»СЋС‡
        
        OpenKey ok = new OpenKey(p, g, y);
        
        CloseKey ck = new CloseKey(x);
        
        KeyHolder holder = new KeyHolder(ok, ck);
        
        return holder;
    }
    
    public static void encode(OpenKey ok, InputStream bais, OutputStream baos) throws IOException {
        
        BigInteger p = ok.getP();
        BigInteger g = ok.getG();
        BigInteger y = ok.getY();
        
        //С€РёС„СЂСѓРµРј
        BigInteger ps = p.subtract(BigInteger.ONE);    
        byte[] zeroBuf = new byte[p.toByteArray().length];
        byte[] readBuf = new byte[zeroBuf.length - 1];
        //ByteArrayInputStream bais = new ByteArrayInputStream(is);
        //ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int readed = 0;
        while (bais.available() > 0) {
            readed = bais.read(readBuf, 1, readBuf.length - 1);
            println("readed = %d", readed);
            
            BigInteger m = new BigInteger(readBuf);
            println("m = %s", m);
                  
            //Р”Р»СЏ С€РёС„СЂРѕРІР°РЅРёСЏ РІС‹Р±РёСЂР°РµС‚СЃСЏ СЃР»СѓС‡Р°Р№РЅРѕРµ С‡РёСЃР»Рѕ k, РІР·Р°РёРјРЅРѕРїСЂРѕСЃС‚РѕРµ СЃ (p - 1)
            //С‚Рѕ РµСЃС‚СЊ РќРћР”(k, (p-1)) = 1
            //BigInteger k = BigInteger.valueOf(7);
   
            EuclidExtended ee = EuclidExtended.generateDualPrimary(ps.bitLength(), ps);
            BigInteger k = ee.b;
            println("k = %s", k);
        
            //РЁРёС„СЂРѕРІР°РЅРёРµРј РїСЂРµРІСЂР°С‰Р°РµРј M РєР°Рє РїР°СЂСѓ a, b
            BigInteger a = g.modPow(k, p); //Math.round(Math.pow(g, k)) % p;
            byte[] abuf = a.toByteArray();
            println(String.format("a = %s", a));

            BigInteger b = y.modPow(k, p).multiply(m).mod(p); //Math.round(Math.pow(y, k) * m) % p;
            byte[] bbuf = b.toByteArray();
            println(String.format("b = %s", b));
            
            baos.write(zeroBuf, 0, zeroBuf.length - abuf.length);
            baos.write(abuf);
            
            baos.write(zeroBuf, 0, zeroBuf.length - bbuf.length);
            baos.write(bbuf);
            
            //readed = bais.read(readBuf, 1, readBuf.length - 1);
            //println("readed = %d", readed);
        }
        
        DataOutputStream dos = new DataOutputStream(baos);
        dos.writeInt(readed);
        println("readed attached %d", readed);
        
    }
    
    public static void decode(OpenKey ok, CloseKey ck, InputStream bais, OutputStream baos) throws IOException {
        
        BigInteger p = ok.getP();
        BigInteger g = ok.getG();
        BigInteger y = ok.getY();
        BigInteger x = ck.getX();
        
        //Р Р°СЃС€РёС„СЂРѕРІС‹РІР°РµРј
        BigInteger pssx = p.subtract(BigInteger.ONE).subtract(x);
        
        byte[] zeroBuf = new byte[p.toByteArray().length];
        byte[] readBuf = new byte[zeroBuf.length - 1];
        //bais = new ByteArrayInputStream(data);
        //baos = new ByteArrayOutputStream();
        byte[] abuf = new byte[zeroBuf.length];
        byte[] bbuf = new byte[zeroBuf.length];
        bais.read(abuf);
        bais.read(bbuf);
        while (bais.available() >= zeroBuf.length) {
            BigInteger a = new BigInteger(abuf);
            BigInteger b = new BigInteger(bbuf);
            
            BigInteger M = a.modPow(pssx, p).multiply(b).mod(p); //(long) (b * (Math.pow(a, p - 1 - x)) % p);
            byte[] mBuf = M.toByteArray();
            println(String.format("M = %d", M));
            
            int zeros = mBuf.length >= readBuf.length ? 0 : readBuf.length - 1 - mBuf.length;
            println("zeros = %d", zeros);
            baos.write(zeroBuf, 0, zeros);
            int from = mBuf.length == readBuf.length ? 1 : 0;
            baos.write(mBuf, from, mBuf.length - from);
            
            bais.read(abuf);
            bais.read(bbuf);
        }
        if (bais.available() > 0) {
            DataInputStream dis = new DataInputStream(bais);
            int left = dis.readInt();
            println("left = %d", left);
            
            BigInteger a = new BigInteger(abuf);
            BigInteger b = new BigInteger(bbuf);
            
            BigInteger M = a.modPow(pssx, p).multiply(b).mod(p); //(long) (b * (Math.pow(a, p - 1 - x)) % p);
            byte[] mBuf = M.toByteArray();
            println(String.format("M = %d length = %d", M, mBuf.length));
            
            int zeros = mBuf.length >= readBuf.length ? 0 : readBuf.length - 1 - mBuf.length;
            println("zeros = %d", zeros);
            baos.write(zeroBuf, 0, zeros);
            
            int from = mBuf.length == readBuf.length ? 1 : 0;
            println("left = %d", left);
            baos.write(mBuf, from, left);
            
        }
        
    }
    
    public static void println(Object o) {
        System.out.println(o);
    }
    
    public static void println(String format, Object o) {
        System.out.println(String.format(format, o));
    }
}
